package chamster.view;

import java.awt.Point;
import java.util.HashSet;
import java.util.Set;

import chamster.BaseHamster;
import chamster.utils.Function1;
import chamster.utils.Graph;
import chamster.utils.Tuple;
import de.hamster.debugger.model.Hamster;
/**
 * Represents the view of a hamster. A view of a Hamster is a map of point and sights.
 * The center of the coordinate space is the hamster (0,0) his left field is (-1, 0)
 * his right field is (1, 0) the field before him is (0, 1).
 * @author mark
 *
 */
public class HamsterView {
	private final Hamster hamster;
	private final int columns;
	private final int rows;
	private final Point hamsterLocationInView;
	private Graph<Sight> viewGraph;
	
	public HamsterView(Hamster ham, int columns, int rows) {
		this.hamster = ham;
		this.columns = columns;
		this.rows = rows;
		this.hamsterLocationInView = new Point(0, 0);
	}
	/**
	 * Updates the view.
	 */
	public void updateView() {
		Sight hamsterSight = this.getSight(this.hamsterLocationInView);
		this.viewGraph = new Graph<Sight>(
				this.getConnections(hamsterSight, new HashSet<Point>()));
	}
	
	private Sight getSight(Point pointInView) {
		Point realPoint = this.viewPointToRealPoint(
				new Point(hamster.getColumn(), hamster.getRow()), pointInView);
		if(realPoint.x > this.columns || realPoint.x < 0 
				|| realPoint.y > this.rows || realPoint.y < 0) {
			return new OutOfAreaSight(pointInView);
		}
		else if(BaseHamster.isWallAt(realPoint)) {
			return new WallInSight(pointInView);
		}
		return new NothingInSight(pointInView);
	}
	
	private Point viewPointToRealPoint(Point hamsterLoc, Point pointInView) {
		int direction = hamster.getBlickrichtung();
		int [][] matrix = new int[2][2];
		if(direction == Hamster.NORTH) {
			matrix[0][0] = 1;
			matrix[0][1] = 0;
			matrix[1][0] = 0;
			matrix[1][1] = 1;
		}
		else if(direction == Hamster.SOUTH) {
			matrix[0][0] = -1;
			matrix[0][1] = 0;
			matrix[1][0] = 0;
			matrix[1][1] = -1;
		}
		else if(direction == Hamster.EAST) {
			matrix[0][0] = 0;
			matrix[0][1] = 1;
			matrix[1][0] = 1;
			matrix[1][1] = 0;
		}
		else if(direction == Hamster.WEST) {
			matrix[0][0] = 0;
			matrix[0][1] = -1;
			matrix[1][0] = -1;
			matrix[1][1] = 0;
		}
		return PointUtils.add(hamsterLoc, 
				PointUtils.multiplyMatrix(matrix, pointInView));
	}
	
	private Set<Tuple<Sight, Sight>> getConnections(Sight sight, 
			Set<Point> markedPoints) {
		markedPoints.add(sight.getLocationInView());
		Set<Tuple<Sight, Sight>> connections = new HashSet<Tuple<Sight,Sight>>();
		Set<Point> newPoints = this.getFieldsNextTo(sight.getLocationInView());
		newPoints.removeAll(markedPoints);
		for(Point p : newPoints) {
			Sight newSight = this.getSight(p);
			if(newSight.canMoveTo()) {
				connections.add(new Tuple<Sight, Sight>(sight, newSight));
				connections.addAll(this.getConnections(newSight, markedPoints));
			}
		}
		return connections;
	}
	
	private Set<Point> getFieldsNextTo(Point p) {
		Set<Point> neighbors = new HashSet<Point>();
		int dx = -1;
		while(dx <= 1) {
			Point newPoint = new Point(p.x + dx, p.y);
			if(this.isInView(newPoint) && !newPoint.equals(p)) {
				neighbors.add(newPoint);
			}
			dx++;
		}
		int dy = -1;
		while(dy <= 1) {
			Point newPoint = new Point(p.x, p.y + dy);
			if(this.isInView(newPoint) && !newPoint.equals(p)) {
				neighbors.add(newPoint);
			}
			dy++;
		}
		return neighbors;
	}
	
	/**
	 * Checks if the given point is a location within the view of the hamster
	 * @param p The point to check
	 * @return
	 */
	private boolean isInView(Point p) {
		return p.getX() >= -2 && p.getX() <= 2 && p.getY() >= 0 && p.getY() <= 2;
	}
	/**
	 * Checks weather the hamster can move to the left.
	 * @return true if the hamster can move to the left
	 */
	public boolean canMoveLeft() {
		Sight hamsterSight = this.getSightFromGraph(new Point(0, 0));
		Sight leftSight = this.getSightFromGraph(new Point(-1, 0));
		boolean canMoveLeft = this.viewGraph.connected(hamsterSight, leftSight);
		return canMoveLeft;
	}
	/**
	 * Checks weather the hamster can move to the right.
	 * @return true if the hamster can move to the right
	 */
	public boolean canMoveRight() {
		Sight hamsterSight = this.getSightFromGraph(new Point(0, 0));
		Sight rightSight = this.getSightFromGraph(new Point(1, 0));
		boolean canMoveLeft = this.viewGraph.connected(hamsterSight, rightSight);
		return canMoveLeft;
	}
	
	private Sight getSightFromGraph(final Point p) {
		return this.viewGraph.findOne(new Function1<Sight, Boolean>() {
			
			public Boolean apply(Sight arg0) {
				return arg0.getLocationInView().equals(p);
			}
		});
	}
	/**
	 * Checks if the Hamster is able to go to the specified point in the view.
	 * @param p The destination
	 * @param The max number of moves to get to the destination
	 * @return Weather the hamster can reach the destination
	 */
	public boolean canMoveTo(Point p,int  maxRecursion) {
		return this.canMoveTo(this.hamsterLocationInView, p, new HashSet<Sight>(), maxRecursion);
	}
	
	private boolean canMoveTo(Point from, Point to, Set<Sight> markedSights, int currentLevel) {
		Sight startSight = this.getSightFromGraph(from);
		Sight endSight = this.getSightFromGraph(to);
		if(this.viewGraph.connected(startSight, endSight)) {
			return true;
		}
		markedSights.add(startSight);
		Set<Sight> connectedSights = this.viewGraph.getConnectedNodes(startSight);
		connectedSights.removeAll(markedSights);
		if(currentLevel >= 0) {
			for(Sight sight : connectedSights) {
				if(this.canMoveTo(sight.getLocationInView(), to, markedSights, currentLevel - 1)) {
					return true;
				}
			}
		}
		return false;
	}
}
